/**
 * Copyright (C) 2012  The CFuture Android SDK Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lurencun.android.toolkit.database;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;

/**
 * SQLite数据库辅助工具，用于创建，打开和管理一个数据库。
 * 在MOTO DEFY机型，ARM CPU 1.0GHz，内存512MB环境下测试，
 * 插入20000（2万）条记录约需要10秒时间。
 * 
 * @author cfuture.chenyoca [桥下一粒砂] (chenyoca@163.com)
 * @date 2012-2-3
 * @version 1.1  
 */
public abstract  class SQLiteDBHelper extends SQLiteOpenHelper {
	
	/**
	 * SQL文件的注释符。
	 * 当SQL文件中的行数据是以 -- （减减）开头，则被认为是注释
	 */
	private final static String SQL_COMMENTS_PREFIX = "--"; 
	private final static String TAG = "SQLiteDBHelper";
	
	private Context mContext;
	private SQLiteDatabase mDatabase;
	
	/**
	 * 创建数据库辅助工具，用于创建，打开和管理一个数据库。
	 * 此方法返回非常快。数据库在没有调用getWritableDatabase() 或者 getReadableDatabase()
	 * 其中的一个方法前，是不会创建或者打开的。
	 * <br/>建议使用 {@link SQLiteDBHelper(Context context, String dbName,int version) }
	 * @param context
	 * 			  Android应用环境变量引用,用于创建或者打开数据库
	 * @param dbName
	 *            数据库名称，即数据库文件名。如果为null则创建一个内存数据库。
	 * @param factory
	 * 			  用于创建Cursor游标对象组，默认为null。
	 * @param version
	 * 	          数据库版本，从1开始。如果已经存在的数据库版本比version要小，则会调用 
	 * 			  onUpgrade(SQLiteDatabase, int, int)升级数据库。否则调用 onDowngrade(SQLiteDatabase, int, int)
	 * 			  降级数据库。
	 */
	public SQLiteDBHelper(Context context, String dbName, CursorFactory factory,int version) {
		super(context, dbName, factory, version);
		mContext = context;
	}
	
	/**
	 * 创建数据库辅助工具，用于创建，打开和管理一个数据库。
	 * 此方法返回非常快。数据库在没有调用getWritableDatabase() 或者 getReadableDatabase()
	 * 其中的一个方法前，是不会创建或者打开的。
	 * <br/>建议使用 {@link SQLiteDBHelper(Context context, String dbName,int version) }
	 * @param context
	 * 			  Android应用环境变量引用,用于创建或者打开数据库
	 * @param dbName
	 *            数据库名称，即数据库文件名。如果为null则创建一个内存数据库。
	 * @param version
	 * 	          数据库版本，从1开始。如果已经存在的数据库版本比version要小，则会调用 
	 * 			  onUpgrade(SQLiteDatabase, int, int)升级数据库。否则调用 onDowngrade(SQLiteDatabase, int, int)
	 * 			  降级数据库。
	 */
	public SQLiteDBHelper(Context context, String dbName,int version) {
		this(context, dbName, null, version);
	}

	/**
	 * 当数据库被第一次创建时调用。
	 * 在此方法中可创建表和初始化表数据。
	 */
	@Override
	final public void onCreate(SQLiteDatabase database) {
		Log.i(TAG," SQLiteDBHelper ==> 创建数据库！");
		executeSQLFile(database,onCreateSQLFile());
	}
	
	/**
	 * 返回数据库被创建时执行的SQL文件名。此文件必须保存在Android项目的assert中。
	 * 在数据库被创建时，该SQL文件将以每行一句SQL语句的方式被执行。
	 * @return 数据库被创建时执行的SQL文件名
	 */
	protected abstract String onCreateSQLFile();
	
	/**
	 * 返回数据库升级时执行的SQL文件名。此文件必须保存在Android项目的assert中。
	 * 在数据库升级时，该SQL文件将以每行一句SQL语句的方式被执行。
	 * @return 数据库升级时执行的SQL文件名
	 */
	protected abstract String onUpgradeSQLFile();
	
	/**
	 * 校验数据库。如果数据库没有被创建，调用此方法将会触发OnCreate()方法创建数据库。
	 * 需要 onCreateSQLFile() 返回创建数据库时执行的SQL文件。
	 */
	final public void verify(){
		mDatabase = this.getReadableDatabase();
		mDatabase.rawQuery("SELECT * FROM sqlite_master;",null).close();
	}
	
	/**
	 * 插入数据
	 * 
	 * @param table
	 *            需要插入数据的表名
	 * @param values
	 *            需要插入的Key-Value键值对。Map对象包含数据行的初始值。
	 *            Key必须是表中的列名，Value必须是表中的列值。
	 * @return 返回最后插入的行ID。如果发生异常，返回 -1 。
	 */
	public long insert(String table, ContentValues values) {
		mDatabase = this.getWritableDatabase();
		return mDatabase.insert(table, null, values);
	}

	/**
	 * 删除数据
	 * 
	 * @param table
	 *            表名
	 * @param whereClause
	 *            执行条件，用?置换
	 * @param whereArgs
	 *            需要置换?的参数
	 * @return 影响行数
	 */
	public int delete(String table, String whereClause, String[] whereArgs) {
		mDatabase = this.getWritableDatabase();
		return mDatabase.delete(table, whereClause, whereArgs);
	}

	/**
	 * 更新数据
	 * 
	 * @param table
	 *            表名
	 * @param values
	 *            需要更新Key-Value键值对
	 * @param whereClause
	 *            更新条件，用?置换
	 * @param whereArgs
	 *            需要置换?的参数
	 * @return 影响行数
	 */
	public int update(String table, ContentValues values, String whereClause,
			String[] whereArgs) {
		mDatabase = this.getWritableDatabase();
		return mDatabase.update(table, values, whereClause, whereArgs);
	}

	/**
	 * 查询数据
	 * 
	 * @param sqlStatement
	 *            SQL语句
	 * @return 查询结果游标对象
	 */
	public Cursor query(String sqlStatement) {
		mDatabase = this.getReadableDatabase();
		return mDatabase.rawQuery(sqlStatement, null);
	}

	/**
	 * 查询数据
	 * 
	 * @param table
	 *            表名
	 * @param columns
	 *            要获取的字段名
	 * @param selection
	 *            查询条件
	 * @param selectionArgs
	 *            条件参数
	 * @return 返回查询结果游标对象
	 */
	public Cursor query(String table, String[] columns, String selection,
			String[] selectionArgs) {
		mDatabase = this.getReadableDatabase();
		return mDatabase.query(table, columns, selection, selectionArgs, null, null,null); 
	}
	
	/**
	 * 执行单句SQL语句，不能是SELECT或者其它带返回数据的SQL语句。
	 * @param sql 需要被执行的SQL语句。不支持用分号（;）分隔的多行语句。
	 */
	public void executeSQL(String sql){
		mDatabase = this.getWritableDatabase();
		mDatabase.execSQL(sql);
	}
	
	/**
	 * 执行单句SQL语句，不能是SELECT/INSERT/UPDATE/DELETE等。
	 * @param sql 需要被执行的SQL语句。不支持用分号（;）分隔的多行语句。
	 * @param bindArgs 只能是byte[], String, Long 和 Double 等类型
	 */
	public void executeSQL(String sql,Object[] bindArgs){
		mDatabase = this.getWritableDatabase();
		mDatabase.execSQL(sql, bindArgs);
	}

	/**
	 * 升级数据库
	 */
	@Override
	final public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
		Log.i(TAG," SQLiteDBHelper ==> 升级数据库！旧版本号："+oldVersion+"，新版本号："+newVersion);
		executeSQLFile(database,onUpgradeSQLFile());
	}
	
	
	
	/**
	 * 读取assets中的SQL文件，并按每行一条的规则执行SQL语句。
	 * @param database SQLiteDatabase对象
	 * @param fileName assets目录中的文件名
	 */
	private void executeSQLFile(SQLiteDatabase database,String fileName){
		try {
			InputStream in = mContext.getAssets().open(fileName);
			BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
			String sql = null;
			while ((sql = bufferedReader.readLine()) != null) {
				if(sql.startsWith(SQL_COMMENTS_PREFIX)) continue;
				if (!TextUtils.isEmpty(sql)) {
					database.execSQL(sql);
				}
			}
			bufferedReader.close();
			in.close();
		} catch (SQLException e) {
			Log.e(TAG,"SQLiteDBHelper 读取SQL文件时发生SQL异常:"+e.getMessage());
		} catch (IOException e) {
			Log.e(TAG,"SQLiteDBHelper 读取SQL文件时发生IO异常:"+e.getMessage());
		}
	}
	
	/**
	 * 关闭数据库
	 * 在使用完数据库后，必须手动关闭。
	 */
	final public void close(){
		if(mDatabase != null) mDatabase.close();
	}
	
}
